本系列文章已出版實體書籍:
「你的地圖會說話?WebGIS 與 JavaScript 的情感交織」(博碩文化)
WebGIS啟蒙首選✖五家地圖API✖近百個程式範例✖實用簡易口訣✖學習難度分級✖補充ES6小知識
本篇文章請搭配
[3D地圖-CesiumJS系列] 一、快速上手
[3D地圖-CesiumJS系列] 二、建立飛航軌跡及動畫
今天要來介紹CesiumJS的粒子系統。
粒子系統是什麼呢?粒子系統(Particle system)是一種圖形技術,可以把許多小圖像的集合來模擬物理現象。
當它們計算過後疊在一起時,會形成複雜的模糊對象,
可以仿造出煙霧、火、天氣現象等效果。
↓ 今天使用的粒子圖像如下,為官方SampleData中提供的smoke.png檔
↓ 將它們疊在一起仿製的煙霧圖像
那就讓我們一步一步開始吧!
在根目錄下建立一個html頁面,取名為Cesium_particle.html。
如果還不會CesiumJS專案建置的人,請參考前天的文章。
↓ 建立一個存放地圖的div
<div id="cmap"></div>
↓ 引入Cesium.js
<script src="../Build/Cesium/Cesium.js"></script>
↓ 引入css
<link rel="stylesheet" href="../Build/Cesium/Widgets/widgets.css" />
↓ css讓地圖滿版
<style>
html,
body,
#cmap {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
↓ 初始化地圖,新建一個Cesium.Viewer的物件,第一個參數存放地圖的容器Id,第二個參數為設定(選填)。
const viewer = new Cesium.Viewer('cmap');
↓ 結果
const start = Cesium.JulianDate.fromDate(new Date("2020-10-14T21:00:00Z"));
const stop = Cesium.JulianDate.addSeconds(start, 100, new Cesium.JulianDate());
viewer.clock.startTime = start.clone();
viewer.clock.stopTime = stop.clone();
viewer.clock.currentTime = start.clone();
viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 結束後循環播放
viewer.clock.multiplier = 1;
viewer.clock.shouldAnimate = true;
viewer.timeline.zoomTo(start, stop);
↓ 時間軸
↓ 設定起始座標及終點座標
const positionStart = Cesium.Cartesian3.fromDegrees(
-75.15787310614596,
39.97862668312678
);
const positionEnd = Cesium.Cartesian3.fromDegrees(
-75.1633691390455,
39.95355089912078
);
↓ Cesium.SampledPositionProperty的物件提供內插計算方法,可以計算每個時間區間相對應的座標
let position = new Cesium.SampledPositionProperty();
position.addSample(start, positionStart); // 填入起始時間及座標
position.addSample(stop, positionEnd); // 填入結束時間及座標
↓ 在地圖上新增車機模型
let entity = viewer.entities.add({
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start: start,
stop: stop,
}),
]),
model: {
uri: "Apps/SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb",
minimumPixelSize: 64,
},
viewFrom: new Cesium.Cartesian3(-150.0, -100.0, 100.0), // 右方150,前方100,上方100
position: position,
orientation: new Cesium.VelocityOrientationProperty(position),
});
↓ 將車機實體設定在地圖上
viewer.trackedEntity = entity;
↓ 結果
要計算粒子隨機排放,並且用4x4矩陣去儲存當下粒子集合的狀態,要使用以下CesiumJS提供的物件及方法。
↓ 排放模型計算工具
const emitterModelMatrix = new Cesium.Matrix4(); // 4x4矩陣
const translation = new Cesium.Cartesian3(); // 3維座標點
const trs = new Cesium.TranslationRotationScale(); // 座標轉換
// 除了3維座標外,加入旋轉角度
const rotation = new Cesium.Quaternion();
// 一種旋轉表達方式,heading表示z軸,pitch表示y軸,roll表示x軸
let hpr = new Cesium.HeadingPitchRoll();
↓ 計算排放模型的陣列
function ComputeEmitterModelMatrix() {
hpr = Cesium.HeadingPitchRoll.fromDegrees(0.0, 0.0, 0.0, hpr);
trs.translation = Cesium.Cartesian3.fromElements(-4.0, 0.0, 1.4, translation);
trs.rotation = Cesium.Quaternion.fromHeadingPitchRoll(hpr, rotation);
return Cesium.Matrix4.fromTranslationRotationScale(trs, emitterModelMatrix);
}
↓ 新增一個物件,用來做為粒子系統的參數設定
let viewModel = {
emissionRate: 5.0,
minimumParticleLife: 1.2,
maximumParticleLife: 1.2,
minimumSpeed: 1.0,
maximumSpeed: 4.0,
startScale: 1.0,
endScale: 5.0,
};
Cesium.ParticleSystem設定
↓ 在地圖上新增粒子系統物件
let particleSystem = viewer.scene.primitives.add(
new Cesium.ParticleSystem({
image: "Apps/SampleData/smoke.png",
startColor: Cesium.Color.LIGHTSEAGREEN.withAlpha(0.7),
endColor: Cesium.Color.WHITE.withAlpha(0.0),
startScale: viewModel.startScale,
endScale: viewModel.endScale,
minimumParticleLife: viewModel.minimumParticleLife,
maximumParticleLife: viewModel.maximumParticleLife,
minimumSpeed: viewModel.minimumSpeed,
maximumSpeed: viewModel.maximumSpeed,
imageSize: new Cesium.Cartesian2(
viewModel.particleSize,
viewModel.particleSize
),
emissionRate: viewModel.emissionRate,
bursts: [
new Cesium.ParticleBurst({
time: 5.0,
minimum: 10,
maximum: 100,
}),
new Cesium.ParticleBurst({
time: 10.0,
minimum: 50,
maximum: 100,
}),
new Cesium.ParticleBurst({
time: 15.0,
minimum: 200,
maximum: 300,
}),
],
lifetime: 16.0,
emitter: new Cesium.CircleEmitter(2.0),
emitterModelMatrix: ComputeEmitterModelMatrix(),
updateCallback: ApplyGravity,
})
);
↓ 粒子系統的updateCallback function
const gravityScratch = new Cesium.Cartesian3();
function ApplyGravity(p, dt) {
Cesium.Cartesian3.normalize(p.position, gravityScratch);
Cesium.Cartesian3.multiplyByScalar(gravityScratch
, viewModel.gravity * dt, gravityScratch);
p.velocity = Cesium.Cartesian3.add(p.velocity
, gravityScratch, p.velocity);
}
↓ 地圖新增視覺更新前的事件,並且在每次更新時,重新計算粒子系統的煙霧排放。
viewer.scene.preUpdate.addEventListener(function (scene, time) {
particleSystem.modelMatrix = entity.computeModelMatrix(time, new Cesium.Matrix4());
particleSystem.emitterModelMatrix = ComputeEmitterModelMatrix();
});
↓ 結果
可以用Cesium.knockout來把粒子設定參數跟dom標籤進行綁定,這邊就不贅述作法,直接使用UI面板修改粒子設定。
↓ 當scale設定很小時,煙霧範圍很小
↓ 當scale設定很大時,煙霧範圍很大
↓ gravity設定很高時,煙霧往上飄
↓ life設定很長時,粒子存在時間很久,煙霧會拉很長
↓ rate設定很快時,煙霧較為密集
假如要計算整個城市裡面的車輛廢氣排放,
或者要監測工廠黑煙排放,
活用CesiumJS的粒子系統想必能一目了然!